#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <windows.h>

#define MAX_WIDTH  2000
#define MAX_HEIGHT 2000

//main function use
#define RET_ERR(fmt,args...) { printf(fmt, ##args);system("pause");return -1;}
#define RET_OK(fmt,args...) { printf(fmt, ##args);system("pause");return 0;}

typedef uint8_t u1;
typedef uint16_t u2;
typedef uint32_t u4;

typedef union {
    u2 value;
    u1 arr[2];
}UN_2BYTE;

typedef union {
    u4 value;
    u1 arr[4];
}UN_4BYTE;

uint32_t bmp_width;
uint32_t bmp_height;
uint32_t bmp_width_real;
uint32_t bmp_bfsize;

void setColor(u1 clr_fr, u1 clr_bg)
{
    HANDLE handle=GetStdHandle(STD_OUTPUT_HANDLE);//获取当前窗口句柄
    
    //[7:4]=clr_bg, [3:0]=clr_fr;
    SetConsoleTextAttribute(handle, clr_bg<<4 | clr_fr);//设置颜色
}

int prtf_string_clr(u1 arr[54], u1 start, u1 size, u2 clr_f, u2 clr_b)
{
    u1 tmp[4];

    if(size != 2 && size != 4)
        return -1;
    
    strncpy(tmp, arr + start, size);
    setColor(clr_f, clr_b);
    if(size == 2)
        printf("%02x %02x ", tmp[0], tmp[1]);
    if(size == 4)
    {
        if((start + 2)%16 != 0)     //16 hex as a line
            printf("%02x %02x %02x %02x ", tmp[0], tmp[1], tmp[2], tmp[3]);
        else 
            printf("%02x %02x\n%02x %02x ", tmp[0], tmp[1], tmp[2], tmp[3]);
    }
    return 0;
}

int prtf_bmp_info(char *type, u1 arr[54], u1 start, u1 size, u2 clr_f, u2 clr_b)
{
    setColor(clr_f, clr_b);
    printf("%17s: ", type);
    UN_2BYTE un2;
    UN_4BYTE un4;
    u1 tmp[4];
    un2.value = 0;
    un4.value = 0;
    if(size != 2 && size != 4)
    {
        return -1;
    }
    strncpy(tmp, arr + start, size);
    if(size == 2)
    {
        un2.arr[0] = tmp[0];
        un2.arr[1] = tmp[1];
        printf("%09d - %02x %02x\n", un2.value, un2.arr[0], un2.arr[1]);
    }
    else if(size == 4)
    {
        un4.arr[0] = tmp[0];
        un4.arr[1] = tmp[1];
        un4.arr[2] = tmp[2];
        un4.arr[3] = tmp[3];
        printf("%09d - %02x %02x %02x %02x\n", un4.value, un4.arr[0], un4.arr[1], un4.arr[2], un4.arr[3]);
    }

    return 0;
}

int bmp_info_check(u1 arr[54])
{
    if(arr[0] != 0x42 || arr[1] != 0x4d)
        RET_ERR("BMP TYPE NOT CORRECT: %x %x\n", arr[0], arr[1]);

    UN_4BYTE un_width;
    UN_4BYTE un_height;
    UN_4BYTE un_bfsize;
    
    un_width.arr[0] = arr[18];
    un_width.arr[1] = arr[19];
    un_width.arr[2] = arr[20];
    un_width.arr[3] = arr[21];
    
    un_height.arr[0] = arr[22];
    un_height.arr[1] = arr[23];
    un_height.arr[2] = arr[24];
    un_height.arr[3] = arr[25];
    
    un_bfsize.arr[0] = arr[2];
    un_bfsize.arr[1] = arr[3];
    un_bfsize.arr[2] = arr[4];
    un_bfsize.arr[3] = arr[5];
    
    bmp_width = un_width.value;
    bmp_height = un_height.value;
    bmp_width_real =  (un_width.value * 24 / 8 + 3) / 4 * 4;
    bmp_bfsize = bmp_width_real * bmp_height + 54;
    
    if(un_bfsize.value == bmp_bfsize || un_bfsize.value-2 == bmp_bfsize)
        return 0;
    else 
        return -1;
}

int check_label(char *filename, char *suffix) 
{
    char *label;
    // printf("name: %s, suffix: %s\n", filename, suffix);
    label = strrchr(filename, '.');
    if(strcmp(label, suffix)) {
        return -1;
    }
    return 0;
}

int main(int argc, char* argv[])
{
    char *fileInName;
    char fileOutName[200];
    FILE *fpIn, *fpOut;
    u1 bmp_head[54];
    u1 line_rgb_data[MAX_WIDTH*3];
    
    fileInName = argv[1];
    // fileOutName = argv[2];
    
    //a.bmp -> a.bmp.txt
    strcpy(fileOutName, fileInName);
    strcat(fileOutName, ".txt");
    //check argument number
    if(argc != 2)
        RET_ERR("argc is don't match! %d\n", argc);
    
    printf("input: %s\noutput: %s\n", fileInName, fileOutName);
    
    //.bmp check
    if(check_label(fileInName, ".bmp"))
        RET_ERR("infile not is .bmp file: %s", fileInName);
    
    if(check_label(fileOutName, ".txt"))
        RET_ERR("outfile not is .txt file: %s", fileOutName);
    
    //new file fp
    fpIn = fopen(fileInName, "rb");
    fpOut = fopen(fileOutName, "w+");
    //文件打开
    if(!fpIn)
        RET_ERR("open failed: %s\n", fileInName);
    
    if(!fpOut)
        RET_ERR("open failed: %s\n", fileOutName);
    
    fseek(fpIn, 0, SEEK_SET);
    fseek(fpOut, 0, SEEK_SET);
    
    fread(bmp_head, 1, 54, fpIn);
    //bmp file format check
    if(bmp_info_check(bmp_head))
        RET_ERR("bmp format error\n");
    
    //file head
    printf("bmp_head: \n");
    prtf_string_clr(bmp_head, 0 , 2, 44, 40); //bfType;
    prtf_string_clr(bmp_head, 2 , 4, 49, 60); //bfSize;
    prtf_string_clr(bmp_head, 6 , 2, 44, 40); //bfReserved1;
    prtf_string_clr(bmp_head, 8 , 2, 49, 60); //bfReserved2;
    prtf_string_clr(bmp_head, 10, 4, 44, 40); //bfOffBits;
    prtf_string_clr(bmp_head, 14, 4, 49, 60); //biSize;         
    prtf_string_clr(bmp_head, 18, 4, 44, 40); //biWidth;        
    prtf_string_clr(bmp_head, 22, 4, 49, 60); //biHeight;       
    prtf_string_clr(bmp_head, 26, 2, 44, 40); //biPlanes;       
    prtf_string_clr(bmp_head, 28, 2, 49, 60); //biBitCount;     
    prtf_string_clr(bmp_head, 30, 4, 44, 40); //biCompression;  
    prtf_string_clr(bmp_head, 34, 4, 49, 60); //biSizeImage;    
    prtf_string_clr(bmp_head, 38, 4, 44, 40); //biXPelsPerMeter;
    prtf_string_clr(bmp_head, 42, 4, 49, 60); //biYPelsPerMeter;
    prtf_string_clr(bmp_head, 46, 4, 44, 40); //biClrUsed;      
    prtf_string_clr(bmp_head, 50, 4, 49, 60); //biClrImportant; 
    
    printf("\n\nbmp header: \n");

    prtf_bmp_info("bfType",         bmp_head, 0 , 2, 44, 40); //
    prtf_bmp_info("bfSize",         bmp_head, 2 , 4, 49, 60); //
    prtf_bmp_info("bfReserved1",    bmp_head, 6 , 2, 44, 40); //
    prtf_bmp_info("bfReserved2",    bmp_head, 8 , 2, 49, 60); //
    prtf_bmp_info("bfOffBits",      bmp_head, 10, 4, 44, 40); //
    
    printf("\nbmp map info: \n");
    
    prtf_bmp_info("biSize",         bmp_head, 14, 4, 49, 60); //
    prtf_bmp_info("biWidth",        bmp_head, 18, 4, 44, 40); //
    prtf_bmp_info("biHeight",       bmp_head, 22, 4, 49, 60); //
    prtf_bmp_info("biPlanes",       bmp_head, 26, 2, 44, 40); //
    prtf_bmp_info("biBitCount",     bmp_head, 28, 2, 49, 60); //
    prtf_bmp_info("biCompression",  bmp_head, 30, 4, 44, 40); //
    prtf_bmp_info("biSizeImage",    bmp_head, 34, 4, 49, 60); //
    prtf_bmp_info("biXPelsPerMeter",bmp_head, 38, 4, 44, 40); //
    prtf_bmp_info("biYPelsPerMeter",bmp_head, 42, 4, 49, 60); //
    prtf_bmp_info("biClrUsed",      bmp_head, 46, 4, 44, 40); //
    prtf_bmp_info("biClrImportant", bmp_head, 50, 4, 49, 60); //
    
    //file write and read
    fprintf(fpOut, "%s\n", fileInName);
    fprintf(fpOut, "bmp_width : %4d\n", bmp_width);
    fprintf(fpOut, "bmp_height: %4d\n", bmp_height);
    fprintf(fpOut, "bmp_bfsize: %d\n", bmp_bfsize);
    fprintf(fpOut, "bmp head: \n\n", bmp_bfsize);
    
    if(bmp_width > MAX_WIDTH || bmp_height > MAX_HEIGHT)
    {
        fprintf(fpOut, "\nbmp file size exceed! w: %d, h: %d\n", bmp_width, bmp_height);
        RET_ERR("bmp file size exceed! w: %d, h: %d", bmp_width, bmp_height);
    }
    
    //write header info map
    for(int idx = 0; idx < 54; idx++)
    {
        fprintf(fpOut, "%02x ", bmp_head[idx]);
        if((idx+1) % 16 == 0)
            fprintf(fpOut, "\n");
    }
    
    fprintf(fpOut, "\n");
    
    for(int h = bmp_height; h > 0; h--)
    {
        fprintf(fpOut, "\n\n/* line: %d B-G-R */\n\n", h);
        fread(line_rgb_data, 1, bmp_width_real, fpIn);
        for(int idx = 0; idx < bmp_width_real; idx++)
        {
            fprintf(fpOut, "%02x", line_rgb_data[idx]);
            if((idx+1) % 15 == 0)
                fprintf(fpOut, "\n");
            else if((idx+1) % 3 == 0)
                fprintf(fpOut, "  ");
            else 
                fprintf(fpOut, " ");
        }
        memset(line_rgb_data, sizeof(line_rgb_data), 0);
    }
    
    if(fclose(fpIn))
    {
        RET_ERR("file close failed: %s\n", fileInName);
    }
    if(fclose(fpOut))
    {
        RET_ERR("file close failed: %s\n", fileOutName);
    }

    fpIn = NULL;
    fpOut = NULL;
    
    system("pause");
    return 0;
}
